package com.zh.demo02.config;

import com.alibaba.fastjson.JSON;
import com.zh.demo02.login.MyUserDetailsService;
import com.zh.demo02.verifycode.sms.SmsCodeService;
import com.zh.demo02.verifycode.VerifyCodeSmsCheckFilter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import java.io.IOException;
import java.util.Map;

/**
 * Spring Security的主配置类
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    /**
     * Spring Security核心配置
     * 过滤器链的重要配置
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //加载不需要权限认证的uri
        String[] permitURIs = new String[]{"/open/**","/code/**"};

        http    //添加自定义的filter
                .addFilterBefore(verifyCodeCheckFilter(), UsernamePasswordAuthenticationFilter.class)

                .authorizeHttpRequests( expression -> {
                    // 静态资源和actuator相关的请求都放行
                    expression.requestMatchers("/static/**", "/actuator/**").permitAll();

                    // 放行要公开的接口
                    expression.requestMatchers(permitURIs).permitAll();

                    // 以角色，去放行
                    expression.requestMatchers("/vip/**").hasAnyRole("VIP1","VIP2"); //有VIP角色才能访问

                    // 其他请求都要认证
                    expression.anyRequest().authenticated();
                })
                //登录配置: 可选 formLogin、oauth2Login 等
                // 指定验证登录的URL
                // 登录成功的逻辑：可以跳转到指定url，也可以自定义Handler来处理
                // 登录失败的逻辑：可以跳转到指定url，也可以自定义Handler来处理
                .formLogin(form -> form
                                //表单页面Url：指定登录页面(可以是任何URL，非必须)，当请求需要认证时就跳转到此URL
//                                .loginPage("/login.html")
                                //提交请求Url：验证登录信息的url，默认和loginPage相同
//                                .loginProcessingUrl("/login")

                                //登录成功后Url：认证成功后的重定向url。successHandler(SavedRequestAwareAuthenticationSuccessHandler)
//                                .defaultSuccessUrl("/")
                                //登录成功后Url：认证成功后的转发url。本质上是 successHandler(ForwardAuthenticationSuccessHandler)
//                                .successForwardUrl("xxxx")
                                //自定义登录成功处理Handler
                                .successHandler(authenticationSuccessHandler())
                                //登录失败后后Url：认证失败后的重定向url，默认和loginPage+"?error"相同。本质上是 failureHandler(SimpleUrlAuthenticationFailureHandler)
//                                .failureUrl("/login?error")
                                //登录失败后后Url：认证失败后的转发url。本质上是 failureHandler(ForwardAuthenticationFailureHandler)
//                                .failureForwardUrl("xxxx")
                                //自定义登录失败处理Handler
                                .failureHandler(authenticationFailureHandler())

                )
                //登出配置
                .logout(logout ->
                        logout
                                //退出登录路径logout
//                                .logoutUrl("/logout")
                                //退出登录成功后Url
//                                .logoutSuccessUrl("/login?logout")
                                //自定义登出成功处理Handler
                                .logoutSuccessHandler(logoutSuccessHandler())
                                .deleteCookies("JSESSIONID")
                )
                //屏蔽默认登陆页面
                .exceptionHandling(eh ->
                        eh.accessDeniedHandler(accessDeniedHandler())
                                .authenticationEntryPoint(myAuthenticationEntryPoint())
                )
                //Session配置
                .sessionManagement(sm -> sm
                                .invalidSessionStrategy(invalidSessionStrategy())  //Session过期后(超过时长)的处理
                                .maximumSessions(1)                 //账号同时登录数,默认超过则把前面的挤下线
//                                .maxSessionsPreventsLogin(true)     //超过则阻止登录操作
                                .expiredSessionStrategy(sessionExpiredStrategy()) //Session被挤掉后(超过登录数)的处理
                )
                .rememberMe(me -> me
                        .tokenValiditySeconds(3600 * 24)
                )
                //csrf安全：post、put等请求需要携带参数DEFAULT_CSRF_PARAMETER_NAME = "_csrf" 或header DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"
//                .csrf(c -> c //关闭
//                        .ignoringRequestMatchers("/login")
//                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
//                )
                .csrf(c -> c.disable())
                .cors(Customizer.withDefaults());

        return http.build();
    }

//==========================================================
// 定义所需的Bean
//==========================================================

    /**
     * 退出登录成功后的处理
     *
     * @return LogoutSuccessHandler
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (req, resp, auth) -> {
            log.info("退出登录成功处理Handler....");

            resp.setStatus(HttpStatus.OK.value());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"msg\":\"您已退出登录\"}");
        };
    }

    /**
     * 登录认证失败后的处理
     *
     * @return AuthenticationFailureHandler
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) -> {
            log.info("登录失败处理Handler.... " + ex.getMessage());

            resp.setStatus(HttpStatus.OK.value());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(Map.of("message", "登录失败 -> " + ex.getMessage())));
        };
    }

    /**
     * 登录认证成功后的处理
     *
     * @return AuthenticationSuccessHandler
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (HttpServletRequest req, HttpServletResponse resp, Authentication auth) -> {
            log.info("登录成功处理Handler.... ");

            Object principal = auth.getPrincipal();

            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(principal));
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new AccessDeniedHandler(){
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse resp, AccessDeniedException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.FORBIDDEN.value());
                try {
                    resp.getWriter().write( JSON.toJSONString(Map.of("message",exception.getMessage())) );
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

    @Bean
    public AuthenticationEntryPoint myAuthenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.UNAUTHORIZED.value());
                resp.getWriter().write(JSON.toJSONString(Map.of("message", exception.getMessage())));
            }
        };
    }

    /**
     * Session过期后(超过时长)的处理
     */
    @Bean
    public InvalidSessionStrategy invalidSessionStrategy() {
        return (HttpServletRequest req, HttpServletResponse resp) -> {
            log.warn("您的session已失效，需要重新登录");

            // 清除JSESSIONID cookie
            Cookie cookie = new Cookie("JSESSIONID", null);
            cookie.setPath(req.getContextPath());
            cookie.setMaxAge(0); // 立即过期
            cookie.setHttpOnly(true);
            resp.addCookie(cookie);

            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"message\":\"您的session已失效，需要重新登录\"}");
        };
    }

    /**
     * Session被挤掉后(超过登录数)的处理
     */
    @Bean
    public SessionInformationExpiredStrategy sessionExpiredStrategy() {
        return (SessionInformationExpiredEvent event) -> {
            log.warn("您已在另一地点登录");

            event.getResponse().setContentType("application/json;charset=UTF-8");
            event.getResponse().getWriter().write("{\"message\":\"您已在另一地点登录\"}");
        };
    }

    @Bean
    public VerifyCodeSmsCheckFilter verifyCodeCheckFilter() {
        return new VerifyCodeSmsCheckFilter(smsCodeService(), authenticationFailureHandler());
    }

    /**
     * 用户user信息服务
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailsService(passwordEncoder());
    }

    /**
     * 密码加密组件
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 验证码生成业务逻辑
     */
    @Bean
    public SmsCodeService smsCodeService() {
        return new SmsCodeService(4, 5 * 60);
    }
}